#include "Viewport.h"

Viewport::Viewport(SDL_Surface* fullScreen, T_Resolution viewportSize, T_Resolution screenResolution)
{
	this->viewportSize = viewportSize;
	this->screenResolution = screenResolution;
	this->originalScreen = fullScreen;

	this->xProp = (float)screenResolution.width / (float)viewportSize.width;
	this->yProp = (float)screenResolution.height / (float)viewportSize.height;

	this->fullScreen = resampleFullScreen(fullScreen, viewportSize, screenResolution);
	this->surfacesToReRender = new List();

	actual_x = 0;
	actual_y = 0;

	x_move = 5;
	y_move = 5;
}

Viewport::~Viewport(void)
{
}

void Viewport::draw() {
	const SDL_VideoInfo* info = SDL_GetVideoInfo();
	int vidFlags = SDL_GL_DOUBLEBUFFER;
	if (info->hw_available == 0) {
		vidFlags |= SDL_SWSURFACE;
	} else {
		vidFlags |= SDL_HWSURFACE;
	}
	int bpp = info->vfmt->BitsPerPixel;
	windowScreen = SDL_SetVideoMode(this->screenResolution.width, this->screenResolution.height, bpp, vidFlags);

	repaintScreen(windowScreen, this->fullScreen, actual_x, actual_y);
}

void Viewport::refreshMovs() {
	Uint8 * keystate = SDL_GetKeyState(NULL);
	while(keystate[SDLK_a] || keystate[SDLK_d] || keystate[SDLK_w] || keystate[SDLK_s]) {
		if (keystate[SDLK_a]) {
			moveLeft(x_move, actual_x);
		}
		if (keystate[SDLK_d]) {
			moveRight(x_move, actual_x);
		}
		if (keystate[SDLK_w]) {
			moveUp(y_move, actual_y);
		}
		if (keystate[SDLK_s]) {
			moveDown(y_move, actual_y);
		}
		render();
		SDL_Delay(10);
		keystate = SDL_GetKeyState(NULL);
		SDL_PumpEvents();
	}
}

void Viewport::moveUp(int& y_move, int& actual_y) {
	if (actual_y < y_move) {
		actual_y = 0;
	} else {
		actual_y -= y_move;
	}
}

void Viewport::moveLeft(int& x_move, int& actual_x) {
	if (actual_x < x_move) {
		actual_x = 0;
	} else {
		actual_x -= x_move;
	}
}

void Viewport::moveRight(int& x_move, int& actual_x) {
	if (actual_x > fullScreen->w - (this->screenResolution.width) - x_move) {
		actual_x = fullScreen->w - (this->screenResolution.width);
	} else {
		actual_x += x_move;
	}
}

void Viewport::moveDown(int& y_move, int& actual_y) {
	if (actual_y > fullScreen->h - (this->screenResolution.height) - y_move) {
		actual_y = fullScreen->h - (this->screenResolution.height);
	} else {
		actual_y += y_move;
	}
}

void Viewport::repaintScreen(SDL_Surface* windowScreen, SDL_Surface* fullScreen, int actual_x, int actual_y) {

	SDL_Rect* fromRect = createRectangle(actual_x, actual_y, this->screenResolution.width, this->screenResolution.height);

	SDL_Rect* toRect = createRectangle(0, 0, this->screenResolution.width, this->screenResolution.height);
	// Copy the new surface to the viewport screen that is showed
	SDL_BlitSurface(fullScreen, fromRect, windowScreen, toRect);
}

SDL_Surface* Viewport::resampleFullScreen(SDL_Surface* fullScreen, T_Resolution viewportSize, T_Resolution screenResolution) {
	return this->resampleScreen(fullScreen, fullScreen->w, fullScreen->h, round(((float)screenResolution.width / (float)viewportSize.width) * (float)fullScreen->w ), round(((float)screenResolution.height / (float)viewportSize.height) * (float)fullScreen->h ));
}

SDL_Surface* Viewport::resampleScreen(SDL_Surface* surfaceToResample, int width_o, int height_o, int width_d, int height_d) {
	
	SDL_Surface* firstPassResampledScreen = SDL_CreateRGBSurface(SDL_HWSURFACE, width_d, height_o, surfaceToResample->format->BitsPerPixel, 0, 0, 0, 0);

	int pixel_x = 0;
	int pixel_y = 0;
	int lastdrawed_x = 0;
	int lastdrawed_y = 0;

	for (int i = 0; i < height_o; i++) {
		lastdrawed_x = 0;
		copyPixel(surfaceToResample, firstPassResampledScreen, 0, i, lastdrawed_x, i);
		for (int j = 0; j < width_o; j++) {
			pixel_x = j;
			pixel_x = round(pixel_x * xProp);

			if (pixel_x - lastdrawed_x >= 1) {

				int diff_x = pixel_x - lastdrawed_x;
				while (diff_x >= 1) {
					lastdrawed_x++;
					copyPixel(surfaceToResample, firstPassResampledScreen, j, i, lastdrawed_x, i);
					diff_x--;
				}
			}

		}
	}

	SDL_Surface* secondPassResampledScreen = SDL_CreateRGBSurface(SDL_HWSURFACE, width_d, height_d, surfaceToResample->format->BitsPerPixel, 0, 0, 0, 0);

	for (int i = 0; i < firstPassResampledScreen->w; i++) {
		lastdrawed_y = 0;
		copyPixel(firstPassResampledScreen, secondPassResampledScreen, i, 0, i, lastdrawed_y);
		for (int j = 0; j < firstPassResampledScreen->h; j++) {
			pixel_y = j;
			pixel_y = round(pixel_y * yProp);

			if (pixel_y - lastdrawed_y >= 1) {

				int diff_y = pixel_y - lastdrawed_y;
				while (diff_y >= 1) {
					lastdrawed_y++;
					copyPixel(firstPassResampledScreen, secondPassResampledScreen, i, j, i, lastdrawed_y);
					diff_y--;
				}
			}

		}
	}

	SDL_FreeSurface(firstPassResampledScreen);	
	return secondPassResampledScreen;
}

void Viewport::copyPixel(SDL_Surface* src_surface, SDL_Surface* dest_surface, Uint32 src_x, Uint32 src_y, Uint32 dest_x, Uint32 dest_y) {

	if (src_surface->format->BytesPerPixel == 4) {

		Uint32* pixmemFrom;
		pixmemFrom = (Uint32*)src_surface->pixels + src_x + (src_y * src_surface->pitch/4);

		Uint32* pixmemTo;
		pixmemTo = (Uint32*)dest_surface->pixels + dest_x + (dest_y * dest_surface->pitch/4);

		memcpy(pixmemTo, pixmemFrom, sizeof(Uint32));

	} 

	if (src_surface->format->BytesPerPixel == 3) {

		Uint8* pixmemFrom;
		pixmemFrom = (Uint8 *)src_surface->pixels + src_y * src_surface->pitch + src_x * 3;

		Uint8* pixmemTo;
		pixmemTo = (Uint8 *)dest_surface->pixels + dest_y * dest_surface->pitch + dest_x * 3;

		for (int i = 0; i < 3; i++) {
			memcpy(&pixmemTo[i], &pixmemFrom[i], sizeof(Uint8));
		}
	}
}

int Viewport::round (double value) {
	int trunk = (int)abs(value);
	if (value - trunk >= 0.5) {
		return trunk + 1;
	}
	return trunk;
}

SDL_Rect* Viewport::createRectangle(int x, int y, int width, int height) {
	SDL_Rect* rect = new SDL_Rect();
	rect->x = x;
	rect->y = y;
	rect->w = width;
	rect->h = height;
	return rect;
}

SDL_Surface* Viewport::getWindowScreen(){
	return windowScreen;
}

SDL_Surface* Viewport::getFullScreen(){
	return fullScreen;
}

void Viewport::addSurfaceToRenderList(SDL_Surface* surface, std::string id, int x, int y, int positionType) {
	SDL_Surface* resampledSurface = null;
	if (positionType == VIEWPORT_ATTACHED) {
		resampledSurface = resampleSurfaceForWindow(surface);
		GraphDrawer::makeTransparent(resampledSurface, Color::white);
	} else {
		resampledSurface = SDL_DisplayFormatAlpha(surface);
	}
	SurfaceContainer* surfaceContainer = new SurfaceContainer(resampledSurface, id, x, y, positionType);
	SurfaceContainerNode* node = new SurfaceContainerNode();
	node->setNodeValue(surfaceContainer);

	for (int i = 0; i < this->surfacesToReRender->getSize(); i++) {
		SurfaceContainer* cont = ((SurfaceContainerNode*)surfacesToReRender->get(i))->getNodeValue();
		if (cont->getId().compare(id) == 0) {
			SurfaceContainerNode* tempNode = (SurfaceContainerNode*)surfacesToReRender->remove(i);
			delete tempNode;
			break;
		}
	}

	this->surfacesToReRender->add(node);
}

void Viewport::updateSurfacePosition(std::string id, int newX, int newY) {
	for (int i = 0; i < this->surfacesToReRender->getSize(); i++) {
		SurfaceContainer* cont = ((SurfaceContainerNode*)surfacesToReRender->get(i))->getNodeValue();
		if (cont->getId().c_str() == id.c_str()) {
			cont->setX(newX);
			cont->setY(newY);
			break;
		}
	}
}

bool Viewport::isVisible(SurfaceContainer* container) {
	bool visible = true;

	if (!((container->getX() * xProp) + container->getSurface()->w > this->actual_x &&
		(container->getX() * xProp) < this->actual_x + this->screenResolution.width)) {
			visible = false;
	}

	if (!((container->getY() * yProp) + container->getSurface()->h > this->actual_y &&
		(container->getY() * yProp) < this->actual_y + this->screenResolution.height)) {
			visible = false;
	}

	return visible;
}

void Viewport::setDirtySurface(std::string id) {
	for (int i = 0; i < this->surfacesToReRender->getSize(); i++) {
		SurfaceContainer* cont = ((SurfaceContainerNode*)surfacesToReRender->get(i))->getNodeValue();
		if (cont->getId().c_str() == id.c_str()) {
			cont->setDirty(true);
			break;
		}
	}
}

void Viewport::drawSurfaceFittedToBorder(SurfaceContainer* surfaceContainer) {
	SDL_Surface* surface = surfaceContainer->getSurface();
	switch (surfaceContainer->getPositionType()) {
		case VIEWPORT_TOP_LEFT :
			{
				SDL_BlitSurface(surface, this->createRectangle(0, 0, surface->w, surface->h),
									windowScreen, createRectangle(surfaceContainer->getX(), surfaceContainer->getY(), surface->w, surface->h));
				break;
			}
		case VIEWPORT_TOP_RIGHT :
			{
				SDL_BlitSurface(surface, this->createRectangle(0, 0, surface->w, surface->h),
									windowScreen, createRectangle(this->screenResolution.width - surface->w - surfaceContainer->getX(), surfaceContainer->getY(), surface->w - surfaceContainer->getX(), surface->h));
				break;
			}
		case VIEWPORT_BOTTOM_LEFT :
			{
				SDL_BlitSurface(surface, this->createRectangle(0, 0, surface->w, surface->h),
									windowScreen, createRectangle(surfaceContainer->getX(), this->screenResolution.height - surface->h - surfaceContainer->getY(), surface->w, surface->h));
				break;
			}
		case VIEWPORT_BOTTOM_RIGHT :
			{
				SDL_BlitSurface(surface, this->createRectangle(0, 0, surface->w, surface->h),
									windowScreen, createRectangle(this->screenResolution.width - surface->w - surfaceContainer->getX(), this->screenResolution.height - surface->h - surfaceContainer->getY(), surface->w, surface->h));
				break;
			}
	}
}

void Viewport::drawSurfaceAttachedToMap(SurfaceContainer* surfaceContainer) {
	int xFrom = 0;
	int yFrom = 0;
	int wFrom = surfaceContainer->getSurface()->w;
	int hFrom = surfaceContainer->getSurface()->h;

	int xTo = surfaceContainer->getX() * this->xProp - actual_x;
	int yTo = surfaceContainer->getY() * this->yProp - actual_y;
	int wTo = surfaceContainer->getSurface()->w;
	int hTo = surfaceContainer->getSurface()->h;

	//if (xTo < 0) {
	//	xFrom = -xTo;
	//	xTo = 0;
	//	wFrom = -xTo;
	//	wTo = -xTo;
	//}

	//if (yTo < 0) {
	//	yFrom = -yTo;
	//	yTo = 0;
	//	hFrom = -yTo;
	//	hTo = -yTo;
	//}


	SDL_BlitSurface(surfaceContainer->getSurface(), this->createRectangle(xFrom, yFrom, wFrom, hFrom),
					windowScreen, createRectangle(xTo, yTo, wTo, hTo));
	
}

void Viewport::render() {
	repaintScreen(windowScreen, this->fullScreen, actual_x, actual_y);
	for (int i = 0; i < surfacesToReRender->getSize(); i++) {
		SurfaceContainer* surfaceContainer = ((SurfaceContainerNode*)surfacesToReRender->get(i))->getNodeValue();
		if (!surfaceContainer->getDirty()) {
			if (surfaceContainer->getPositionType() == VIEWPORT_ATTACHED && isVisible(surfaceContainer)) {
				drawSurfaceAttachedToMap(surfaceContainer);
			}
			if (surfaceContainer->getPositionType() != VIEWPORT_ATTACHED) {
				drawSurfaceFittedToBorder(surfaceContainer);
			}
		}
	}
	
	SDL_Flip(windowScreen);
}

SDL_Surface* Viewport::resampleSurfaceForWindow(SDL_Surface* surface) {
	return resampleScreen(surface, surface->w, surface->h, round(surface->w * xProp), round(surface->h * yProp));
}
